package demo.impl.ipv4;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import org.pcap4j.core.PcapAddress;
import org.pcap4j.core.PcapNetworkInterface;
import org.pcap4j.packet.EthernetPacket;
import org.pcap4j.packet.IpV4Packet;
import org.pcap4j.packet.Packet;
import org.pcap4j.packet.namednumber.IpNumber;
import org.pcap4j.util.ByteArrays;
import org.pcap4j.util.MacAddress;
import org.slf4j.LoggerFactory;

import demo.api.IPv4Service;
import demo.api.ProtocolService;
import demo.impl.AbstractProtocolService;
import demo.util.AssertUtils;

/**
 * 与特定网卡相关的网络信息
 * 
 * @author caohj
 * @date 2017年8月5日
 */
public class NetworkInfo {

	/** 网卡IP地址 */
	private InetAddress localhost;
	/** 子网掩码 */
	private InetAddress networkMask;
	/** 网关MAC */
	private volatile MacAddress gatewayMac;

	/**
	 * 获取网卡绑定的IP地址和子网掩码
	 * 
	 * @param nif
	 * @return
	 */
	private static NetworkInfo loadNifIPAddressAndNetmask(PcapNetworkInterface nif) {
		NetworkInfo networkInfo = new NetworkInfo();
		List<PcapAddress> addresses = nif.getAddresses();
		if (addresses != null) {
			for (PcapAddress address : addresses) {
				if (address.getAddress() instanceof Inet4Address) {
					networkInfo.setLocalhost(address.getAddress());
					networkInfo.setNetworkMask(address.getNetmask());
					break;
				}
			}
		}
		if (networkInfo.getLocalhost() == null || networkInfo.getNetworkMask() == null) {
			throw new RuntimeException("获取子网信息失败！localhost=" + networkInfo.getLocalhost() + ", networkMask="
					+ networkInfo.getNetworkMask());
		}
		return networkInfo;
	}

	/**
	 * 获取指定网卡的网络信息
	 * 
	 * @param ipv4
	 * @param nif
	 * @return
	 */
	@SuppressWarnings("rawtypes")
	public static NetworkInfo loadNetworkInfo(IPv4Service ipv4, PcapNetworkInterface nif) {
		AssertUtils.notNull(ipv4, "参数ipv4不能为空！获取网络信息暂时需要借助于ipv4服务");
		AssertUtils.notNull(nif, "参数网络接口nif不能为空！");

		// 我们需要搜集的网关信息
		final NetworkInfo networkInfo = loadNifIPAddressAndNetmask(nif);

		// 同步信号锁
		final CountDownLatch latch = new CountDownLatch(1);

		// 已注册的UDP服务，因为我们需要借助于替换的UDP服务
		final ProtocolService last = ipv4.getHighLevelProtocal(IpNumber.UDP.value());

		// 假的UDP服务，仅仅获取网关完成就注销
		ProtocolService dummyProtocol = new AbstractProtocolService(null) {

			@Override
			protected Number getProtocolType() {
				return IpNumber.UDP.value();
			}

			@Override
			protected Number getHighLevelProtocolType(Packet packet) {
				throw new RuntimeException("不支持的调用：在dummyProtocol上调用getHighLevelProtocolType.");
			}

			public void gotPacket(Packet packet) {
				if (networkInfo.getGatewayMac() == null) {
					if (packet.contains(IpV4Packet.class)) {
						IpV4Packet ipv4Packet = packet.get(IpV4Packet.class);
						Inet4Address destIp = ipv4Packet.getHeader().getDstAddr();
						// 如果目标地址不在子网中，那么通信必须借助于网关
						if (!networkInfo.isSameSubnet(destIp)) {
							// 拿到网关地址
							networkInfo.setGatewayMac(packet.get(EthernetPacket.class).getHeader().getDstAddr());
							// 通知外部网关信息已拿到
							latch.countDown();
						}
					}
				}

				if (last != null) {
					last.gotPacket(packet);
				}
			};
		};

		// 将假的UDP服务注册到协议栈上
		ipv4.installHighLevelProtocal(IpNumber.UDP.value(), dummyProtocol);
		try {
			// 发送UDP数据，以便包监听器可以监听到网关地址
			DatagramSocket socket = new DatagramSocket();
			byte[] testdata = "12345".getBytes();
			socket.send(new DatagramPacket(testdata, testdata.length, InetAddress.getByName("www.baidu.com"), 80));
			socket.close();

			if (!latch.await(30, TimeUnit.SECONDS)) {
				throw new RuntimeException("等待网关Ip数据报超时，无法获取网关地址，超时时间=30S");
			}
		} catch (Exception e) {
			if (networkInfo.getGatewayMac() == null) {
				throw new RuntimeException("获取局域网网关地址失败：" + e.getMessage(), e);
			} else {
				// 如果网关已获取，其他的错误都是无关紧要了
			}
		} finally {
			if (last == null) {
				ipv4.uninstallHighLevelProtocal(IpNumber.UDP.value());
			} else {
				ipv4.installHighLevelProtocal(IpNumber.UDP.value(), last);
			}
		}

		LoggerFactory.getLogger(NetworkInfo.class).info(
				"获取网络信息：localhost=" + networkInfo.getLocalhost() + ", networkMask=" + networkInfo.getNetworkMask()
						+ ", gatewayMAC=" + networkInfo.getGatewayMac());
		return networkInfo;
	}

	/**
	 * 给定ip是否位于同一个网段
	 * 
	 * @param dest
	 * @return
	 */
	public boolean isSameSubnet(InetAddress dest) {
		int subnet = ByteArrays.getInt(localhost.getAddress(), 0) & ByteArrays.getInt(networkMask.getAddress(), 0);
		int destSubnet = ByteArrays.getInt(dest.getAddress(), 0) & ByteArrays.getInt(networkMask.getAddress(), 0);
		return subnet == destSubnet;
	}

	public InetAddress getLocalhost() {
		return localhost;
	}

	public void setLocalhost(InetAddress localhost) {
		this.localhost = localhost;
	}

	public InetAddress getNetworkMask() {
		return networkMask;
	}

	public void setNetworkMask(InetAddress networkMask) {
		this.networkMask = networkMask;
	}

	public MacAddress getGatewayMac() {
		return gatewayMac;
	}

	public void setGatewayMac(MacAddress gatewayMac) {
		this.gatewayMac = gatewayMac;
	}

}
